﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SharpMUD.Framework.ComponentModel;

namespace SharpMUD.Framework.Services.Configuration
{
    [AttributeUsage(AttributeTargets.Class,AllowMultiple=true,Inherited=true)]
    public sealed class ServiceDependencyAttribute
        : System.Attribute
    {
        private List<System.Type> _Dependencies;

        private ServiceDependencyAttribute() { }
        public ServiceDependencyAttribute(params System.Type[] dependencies)
        {
            _Dependencies = new List<Type>(dependencies);
        }

        public IEnumerable<Type> GetDependencies()
        {
            return _Dependencies.Where((t) => typeof(IService).IsAssignableFrom(t)).AsEnumerable();
        }

        private static readonly Dictionary<Type, IEnumerable<Type>> _AppliedDependencyCache = new Dictionary<Type, IEnumerable<Type>>();

        public static IEnumerable<Type> GetAppliedDependencies(System.Type type)
        {
            if (!typeof(IService).IsAssignableFrom(type))
                throw new ArgumentException("The given type must implement the IService interface");

            if (_AppliedDependencyCache.ContainsKey(type))
                return _AppliedDependencyCache[type];

            var results = type.GetCustomAttributes(typeof(ServiceDependencyAttribute), true)
                        .Cast<ServiceDependencyAttribute>().SelectMany((a) => a.GetDependencies());

            _AppliedDependencyCache[type] = results;

            return results;
        }


        private static readonly Dictionary<Type, IEnumerable<Type>> _EffectiveDependencyCache = new Dictionary<Type, IEnumerable<Type>>();

        public static IEnumerable<Type> GetEffectiveDependencies(System.Type type)
        {
            if (!typeof(IService).IsAssignableFrom(type))
                throw new ArgumentException("The given type must implement the IService interface");

            if (_EffectiveDependencyCache.ContainsKey(type))
                return _EffectiveDependencyCache[type];

            List<Type> exploredTypes = new List<Type>();
            List<Type> unexploredTypes = new List<Type>();

            unexploredTypes.AddRange(GetAppliedDependencies(type));

            while (unexploredTypes.Count > 0)
            {
                List<Type> newlyDiscoveredTypes = new List<Type>();
                foreach (Type t in unexploredTypes)
                {
                    if (_EffectiveDependencyCache.ContainsKey(t))
                    {
                        exploredTypes.Add(t);
                        exploredTypes.AddRange(_EffectiveDependencyCache[t]);
                    }
                    else
                    {
                        IEnumerable<Type> currentDependencies = GetAppliedDependencies(t);
                        newlyDiscoveredTypes.AddRange(currentDependencies.Where(
                            (ty) => !exploredTypes.Contains(ty) &&
                                    !unexploredTypes.Contains(ty) &&
                                    !newlyDiscoveredTypes.Contains(ty)
                                    ));
                    }
                }
                exploredTypes.AddRange(unexploredTypes.Where((t)=>!exploredTypes.Contains(t)));
                unexploredTypes.Clear();
                unexploredTypes.AddRange(newlyDiscoveredTypes);
            }

            _EffectiveDependencyCache[type] = exploredTypes;
            return exploredTypes;
        }

        public static IEnumerable<Type> SortByDependencies(IEnumerable<Type> types)
        {
            var typesAndDependencies =
                from t in types
                select new { T = t, Dp = GetEffectiveDependencies(t) };

            typesAndDependencies =
                from p in typesAndDependencies
                orderby p.Dp.Count() ascending
                select p;

            return typesAndDependencies.Select(p => p.T);
        }
    }
}
